/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.parser;

import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.util.*;

import org.xml.sax.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Stack;

/**
 * This class establishes the in-memory construction of Definition
 * objects read, and does the callback on the storage interface.
 * This class is the content handler for the XML document being parsed.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 *
 * @see Definition
 * @see DefinitionHandler 
 */
public class VDLContentHandler implements ContentHandler
{
  /**
   * This is the callback handler for ready Definitions.
   */
  private DefinitionHandler m_callback;

  /**
   * This is the callback handler for a Definition outside Definitions.
   */
  private FinalizerHandler m_finalize;

  /**
   * Keep the location within the document
   */
  private Locator m_location;

  /**
   * A Hashmap to forward resolve namespaces that were encountered
   * during parsing.
   */
  private Map m_forward;

  /**
   * A Hashmap to reverse resolve namespaces that were encountered
   * during parsing.
   */
  private Map m_reverse;

  /**
   * The meta elements needs some very special handling (only bypassed).
   */
  private int m_metamode = 0;

  /**
   * Count the depths of elements in the document
   */
  private int m_depth = 0;

  /**
   *
   */
  private Stack m_stack;
    
  /**
   * ctor. 
   */
  public VDLContentHandler()
  {
    this.m_callback = null;
    this.m_finalize = null;
    this.m_forward = new HashMap();
    this.m_reverse = new HashMap();
  }

  /**
   * Accessor: This function allows a different DefinitionHandler to 
   * be set and used. 
   * @param ds is the new callback object to handle each Definition
   * as it becomes ready.
   */
  public void setDefinitionHandler( DefinitionHandler ds )
  {
    this.m_callback = ds;
  }

  /**
   * Accessor: This function allows a different FinalizerHandler to 
   * be set and used. Note, this cannot be used for the Definitions
   * elements. In order to reduce the memory footprint, the Definitions
   * element will not be maintained!
   *
   * @param fh is the new callback object to handle a single top-level
   * VDL element as it becomes ready.
   */
  public void setFinalizerHandler( FinalizerHandler fh )
  {
    this.m_finalize = fh;
  }

  /**
   * Obtains the document locator from the parser. The document location
   * can be used to print debug information, i.e the current location
   * (line, column) in the document.
   *
   * @param locator is the externally set current position 
   */
  public void setDocumentLocator( Locator locator )
  {
    this.m_location = locator;
  }

  /**
   * This method specifies what to do when the parser is at the beginning
   * of the document. In this case, we simply print a message for debugging.  
   */
  public void startDocument()   
    throws SAXException
  {
    this.m_depth = 0;
    this.m_stack = new Stack();
    Logging.instance().log( "parser", 1, ">>> start of document >>>" );
  }

  /**
   * This method specifies what to do when the parser reached the end
   * of the document. In this case, we simply print a message for debugging.  
   */
  public void endDocument()
    throws SAXException
  {
    Logging.instance().log( "parser", 1, "<<< end of document <<<" );
  }

  /**
   * There is a prefix or namespace defined, put the prefix and its URI
   * in the HashMap. We can get the URI when the prefix is used here after.
   *
   * @param prefix the Namespace prefix being declared.
   * @param uri the Namespace URI the prefix is mapped to.
   */
  public void startPrefixMapping( java.lang.String prefix,
				  java.lang.String uri )
    throws SAXException
  {
    String p = prefix == null ? null : new String(prefix);
    String u = uri == null ? null : new String(uri);
    Logging.instance().log( "parser", 2, "adding \"" + p + "\" <=> " + u );

    if ( ! this.m_forward.containsKey(p) ) 
      this.m_forward.put(p, new Stack());
    ((Stack) this.m_forward.get(p)).push(u);

    if ( ! this.m_reverse.containsKey(u) )
      this.m_reverse.put(u, new Stack());
    ((Stack) this.m_reverse.get(u)).push(p);
  }

  /**
   * Out of the reach of the prefix, remove it from the HashMap.
   *
   * @param prefix is the prefix that was being mapped previously.
   */ 
  public void endPrefixMapping( java.lang.String prefix )
    throws SAXException
  {
    String u = (String) ((Stack) this.m_forward.get(prefix)).pop();
    String p = (String) ((Stack) this.m_reverse.get(u)).pop();
    Logging.instance().log( "parser", 2, "removed \"" + p + "\" <=> " + u );
  }

  /**
   * Helper function to map prefixes correctly onto the elements.
   *
   * @param uri is the parser-returned URI that needs translation.
   * @return the correct prefix for the URI
   */
  private String map( String uri )
  {
    if ( uri == null || uri.length() == 0 ) return "";
    Stack stack = (Stack) this.m_reverse.get(uri);
    String result = stack == null ? null : (String) stack.peek();
    if ( result == null || result.length() == 0 ) return "";
    else return result + ':';
  }

  /**
   * This method defines the action to take when the parser begins to parse
   * an element.
   *
   * @param namespaceURI is the URI of the namespace for the element
   * @param localName is the element name without namespace
   * @param qName is the element name as it appears in the docment
   * @param atts has the names and values of all the attributes
   */
  public void startElement( java.lang.String namespaceURI,
			    java.lang.String localName,
			    java.lang.String qName,
			    Attributes atts )
    throws SAXException
  {
    Logging.instance().log( "parser", 3, 
			    "<" + map(namespaceURI) + localName + "> at " +
			    m_location.getLineNumber() + ":" +
			    m_location.getColumnNumber() );
    m_depth++; 

    // if in meta mode, skip filling in new elements
    if ( this.m_metamode == 0 ) {
      java.util.List names = new java.util.ArrayList();
      java.util.List values = new java.util.ArrayList();
      for ( int i=0; i < atts.getLength(); ++i ) {
	String name = new String( atts.getLocalName(i) );
	String value = new String( atts.getValue(i) );

	Logging.instance().log( "parser", 2, "attribute " + 
				map(atts.getURI(i)) +
				name + "=\"" + value + "\"" );
	names.add(name);
	values.add(value);
      }

      VDL object = createObject( qName, names, values );
      if ( object != null ) 
	m_stack.push( new StackElement( qName, object ) );
      else
	throw new SAXException( "empty element while parsing" );
    }

    // check for start of meta mode
    if ( localName.equals("meta") && 
	 namespaceURI.equals( Definitions.SCHEMA_NAMESPACE ) ) {
      // increase meta level
      this.m_metamode++;
    }
  }

  /**
   * The parser is at the end of an element. Each successfully and
   * completely parsed Definition will trigger a callback to the
   * registered DefinitionHandler.
   *
   * @param namespaceURI is the URI of the namespace for the element
   * @param localName is the element name without namespace
   * @param qName is the element name as it appears in the docment
   */
  public void endElement( java.lang.String namespaceURI,
			  java.lang.String localName,
			  java.lang.String qName )
    throws SAXException
  {
    m_depth--; 
    Logging.instance().log( "parser", 3, 
			    "</" + map(namespaceURI) + localName + "> at " +
			    m_location.getLineNumber() + ":" +
			    m_location.getColumnNumber() );

    // check for end of meta mode
    if ( localName.equals("meta") && 
	 namespaceURI.equals( Definitions.SCHEMA_NAMESPACE ) ) {
      // decrease meta level
      --this.m_metamode;
    }

    // if in meta mode, skip filling in new elements, as they won't 
    // belong to VDLx (or they might, but should be ignored).
    if ( this.m_metamode == 0 ) {
      StackElement tos = (StackElement) m_stack.pop();
      if ( ! qName.equals(tos.m_name) ) {
	Logging.instance().log( "default", 0, "assertion failure" );
	System.exit(1);
      }

      if ( ! m_stack.empty() ) {
	// add pieces to lower levels
	StackElement peek = (StackElement) m_stack.peek();
	if ( ! setElementRelation( peek.m_name.charAt(0), peek.m_obj, tos.m_obj ) )
	  Logging.instance().log( "parser", 0, "Element " + tos.m_name +
				  " does not fit into element " + peek.m_name );
      } else {
	// run finalizer, if available
	if ( m_finalize != null ) m_finalize.store(tos.m_obj);
      }
    }
  }

  /**
   * This method is the callback function for characters in an element.
   * The element is expected to be of mixed content.
   * 
   * @param ch are the characters from the XML document
   * @param start is the start position into the array
   * @param length is the amount of valid data in the array
   */
  public void characters( char[] ch, int start, int length )
    throws SAXException
  {
    String message = new String( ch, start, length );
    if ( message.length() > 0  ) {
      if ( message.trim().length() == 0 )
	Logging.instance().log( "parser", 3, "Characters: \' \' x " + 
				message.length() );
      else 
	Logging.instance().log( "parser", 3,
				"Characters: \"" + message  + "\"" );
      if ( this.m_metamode == 0 ) {
	// insert text into the only elements possible to carry text
	StackElement tos = (StackElement) m_stack.peek();
	if ( tos.m_obj instanceof Text ) {
	  Text text = (Text) tos.m_obj;
	  String old = text.getContent();
	  if ( old == null ) text.setContent(message);
	  else text.setContent( old + message );
	  this.log( "Text", tos.m_name, message );
	} else if ( tos.m_obj instanceof Meta ) {
	  Meta meta = (Meta) tos.m_obj;
	  String content = message.trim();
	  meta.addContent(content);
	  this.log( "Meta", tos.m_name, content );
	}
      }
    }
  }

  /**
   * Currently, ignorable whitespace will be ignored.
   *
   * @param ch are the characters from the XML document
   * @param start is the start position into the array
   * @param length is the amount of valid data in the array
   */
  public void ignorableWhitespace( char[] ch, int start, int length )
    throws SAXException
  {
    // not implemented
  }

  /**
   * Receive a processing instruction. Currently, we are just printing
   * a debug message that we received a PI.
   *
   * @param target the processing instruction target
   * @param data the processing instruction data, or null if none was supplied.
   * The data does not include any whitespace separating it from the target.
   */
  public void processingInstruction( java.lang.String target,
				     java.lang.String data )
    throws SAXException
  {
    Logging.instance().log( "parser", 2, "processing instruction " + target +
			    "=\"" + data + "\" was skipped!");
  }

  /**
   * Receive a notification that an entity was skipped. Currently, we
   * are just printing a debug message to this fact.
   *
   * @param name The name of the skipped entity. If it is a parameter
   * entity, the name will begin with '%', and if it is the external DTD
   * subset, it will be the string "[dtd]". 
   */
  public void skippedEntity(java.lang.String name)
    throws SAXException
  {
    Logging.instance().log( "parser", 2, 
			    "entity " + name + " was skipped!");
  }

  //
  // =================================================== our own stuff ===
  //
  
  /**
   * Small helper method to bundle repetitive parameters in a template
   * for reporting progress.
   *
   * @param subject is the name of the XML element that is being scrutinized.
   * @param name is then name of the element we are working with.
   * @param value is the attribute value.
   */
  private void log( String subject, String name, String value )
  {
    if ( value == null ) value = new String();
    Logging.instance().log( "filler", 3, subject + "." + name + "=\"" +
			    value + "\"" );
  }
  
  /**
   * Small helper method to bundle repetitive complaints in a template
   * for reporting progress.
   *
   * @param subject is the name of the XML element that is being scrutinized.
   * @param name is then name of the element we are working with.
   * @param value is the attribute value.
   */
  private void complain( String subject, String name, String value )
  {
    if ( value == null ) value = new String();
    Logging.instance().log( "default", 0, "ignoring " + subject + '@' + name +
			    "=\"" + value + '"', true );
  }

  /**
   * This method determines the actively parsed element, creates the
   * Java object that corresponds to the element, and sets the member 
   * variables with the values of the attributes of the element.
   *
   * @param e is the name of the element
   * @param names is a list of attribute names, as strings.
   * @param values is a list of attribute values, to match the key list.
   * @return A new VDL Java object, which may only be partly constructed.
   * @exception IllegalArgumentException if the element name is too short.
   */
  protected VDL createObject( String e, java.util.List names, 
			      java.util.List values )
    throws IllegalArgumentException
  {
    if ( e == null || e.length() < 1 ) 
      throw new IllegalArgumentException("illegal element length");

    // postcondition: string has content w/ length > 0
    switch ( e.charAt(0) ) {
      //
      // A
      //
    case 'a':
      if ( e.equals("argument") ){
	Argument argument = new Argument();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("name") ) {
	    this.log( e, name, value );
	    argument.setName(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return argument;
      }
      // unknown 
      return null;

      //
      // C
      //
    case 'c':
      if ( e.equals("call") ) {
	Call call = new Call();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("minIncludeVersion") ) {
	    this.log( e, name, value );
	    call.setMinIncludeVersion(value);
	  } else if ( name.equals("maxIncludeVersion") ) {
	    this.log( e, name, value );
	    call.setMaxIncludeVersion(value);
	  } else if ( name.equals("usesspace") ) {
	    this.log( e, name, value );
	    call.setUsesspace(value);
	  } else if ( name.equals("uses") ) {
	    this.log( e, name, value );
	    call.setUses(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return call;
      }

      // unknown 
      return null;


      //
      // D
      //
    case 'd':
      if ( e.equals("declare") ) {
	Declare declaration = new Declare("", 0);
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("name") ) {
	    this.log( e, name, value );
	    declaration.setName(value);
	  } else if ( name.equals("container") ) {
	    this.log( e, name, value );
	    declaration.setContainerType( VDLType.getContainerType(value) );
	  } else if ( name.equals("link") ) {
	    this.log( e, name, value );
	    declaration.setLink( VDLType.getLinkType(value) );
	  } else {
	    this.complain( e, name, value );
	  }
	}

	return declaration;

      } else if ( e.equals("definitions") ) {
	Definitions definitions = new Definitions();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("vdlns") ) {
	    this.log( e, name, value );
	    definitions.setVdlns(value);
	  } else if ( name.equals("version") ) {
	    this.log( e, name, value );
	    definitions.setVersion(value);
	  } else if (name.equals("schemaLocation") ) {
	    // ignore
	  } else {
	    this.complain( e, name, value );
	  }
	}

	return definitions;

      } else if ( e.equals("derivation") ){
	Derivation derivation= new Derivation();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("minIncludeVersion") ) {
	    this.log( e, name, value );
	    derivation.setMinIncludeVersion(value);
	  } else if ( name.equals("maxIncludeVersion") ) {
	    this.log( e, name, value );
	    derivation.setMaxIncludeVersion(value);
	  } else if ( name.equals("name") ) {
	    this.log( e, name, value );
	    derivation.setName(value);
	  } else if ( name.equals("namespace") ) {
	    this.log( e, name, value );
	    derivation.setNamespace(value);
	  } else if ( name.equals("description") ) {
	    this.log( e, name, value );
	    derivation.setDescription(value);
	  } else if ( name.equals("keyword") ) {
	    this.log( e, name, value );
	    derivation.setKeyword(value);
	  } else if ( name.equals("title") ) {
	    this.log( e, name, value );
	    derivation.setTitle(value);
	  } else if ( name.equals("url") ) {
	    this.log( e, name, value );
	    derivation.setUrl(value);
	  } else if ( name.equals("usesspace") ) {
	    this.log( e, name, value );
	    derivation.setUsesspace(value);
	  } else if ( name.equals("uses") ) {
	    this.log( e, name, value );
	    derivation.setUses(value);
	  } else if ( name.equals("version") ) {
	    this.log( e, name, value );
	    derivation.setVersion(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return derivation;
      }

      // unknown 
      return null;

      //
      // L 
      //
    case 'l':
      if ( e.equals("lfn") ) { // remove const-lfn from here
	LFN lfn = new LFN();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("file") ) {
	    this.log( e, name, value );
	    lfn.setFilename(value);
	  } else if (name.equals("temporaryHint")) {
	    this.log( e, name, value );
	    lfn.setTemporary( value );
	  } else if (name.equals("dontRegister")) {
	    this.log( e, name, value );
	    lfn.setDontRegister( Boolean.valueOf(value).booleanValue() );
	  } else if (name.equals("optional")) {
	    this.log( e, name, value );
	    lfn.setOptional( new Boolean(value).booleanValue() );
	  } else if (name.equals("dontTransfer")) {
	    if ( value.equals("false") ) {
	      this.log( e, name, value );
	      lfn.setDontTransfer( LFN.XFER_MANDATORY );
	    } else if ( value.equals("true") ) {
	      this.log( e, name, value );
	      lfn.setDontTransfer( LFN.XFER_NOT );
	    } else if ( value.equals("optional") ) {
	      this.log( e, name, value );
	      lfn.setDontTransfer( LFN.XFER_OPTIONAL );
	    } else {
	      this.complain( e, name, value );
	    }
	  } else if (name.equals("isTemporary")) {
	    // deprecated work-around until phased out.
	    this.log( e, name, value );
	    // FIXME: check for null 
	    Logging.instance().log( "app", 0, 
		"using deprecated attribute \"isTemporary\" in LFN " + 
		lfn.getFilename() );
	    boolean temp = Boolean.valueOf(value).booleanValue();
	    lfn.setDontRegister( temp );
	    lfn.setDontTransfer( temp ? LFN.XFER_NOT : LFN.XFER_MANDATORY );
	  } else if ( name.equals("link") ) {
	    this.log( e, name, value );
	    int link = 	VDLType.getLinkType(value);
	    lfn.setLink(link);
	  } else {
	    this.complain( e, name, value );
	  }
	} // for
	return lfn;

      } else if ( e.equals("local") ) {
	Local temp = new Local("", 0);
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("name") ) {
	    this.log( e, name, value );
	    temp.setName(value);
	  } else if ( name.equals("container") ) {
	    this.log( e, name, value );
	    temp.setContainerType( VDLType.getContainerType(value) );
	  } else if ( name.equals("link") ) {
	    this.log( e, name, value );
	    temp.setLink( VDLType.getLinkType(value) );
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return temp;

      } else if ( e.equals("list") ) {
	org.griphyn.vdl.classes.List list = 
	  new org.griphyn.vdl.classes.List();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);
	  this.complain( e, name, value );
	}
	return list;
      }

      // unknown 
      return null;

      //
      // M
      //
    case 'm':
      if ( e.equals("meta") ) {
	Logging.instance().log("app", 2, "entering meta mode" );
	return new Meta();
      }

      // unknown
      return null;

      //
      // P
      //
    case 'p':
      if ( e.equals("profile") ) {
	Profile prof = new Profile();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);
	  
	  if ( name.equals("key") ) {
	    this.log( e, name, value );
	    prof.setKey(value);
	  } else if ( name.equals("namespace") ) {
	    this.log( e, name, value );
	    prof.setNamespace(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}

	return prof;

      } else if ( e.equals("pass") ) {
	Pass pass = new Pass();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("bind") ){
	    this.log( e, name, value );
	    pass.setBind(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return pass;
      }

      // unknown 
      return null;

      //
      // S
      //
    case 's':
      if ( e.equals("scalar") ) {
	Scalar scalar = new Scalar();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);
	  this.complain( e, name, value );
	}
	return scalar;
      }

      // unknown 
      return null;

      //
      // T
      //
    case 't':
      if ( e.equals("text") ) {
	return new Text();
      } else if ( e.equals("transformation") ) {
	Transformation trans = new Transformation();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("version") ) {
	    this.log( e, name, value );
	    trans.setVersion(value);
	  } else if ( name.equals("name") ) {
	    this.log( e, name, value );
	    trans.setName(value);
	  } else if ( name.equals("namespace") ) {
	    this.log( e, name, value );
	    trans.setNamespace(value);
	  } else if ( name.equals("description") ) {
	    this.log( e, name, value );
	    trans.setDescription(value);
	  } else if ( name.equals("keyword") ) {
	    this.log( e, name, value );
	    trans.setKeyword(value);
	  } else if ( name.equals("title") ) {
	    this.log( e, name, value );
	    trans.setTitle(value);
	  } else if ( name.equals("url") ) {
	    this.log( e, name, value );
	    trans.setUrl(value);
	  } else if ( name.equals("version") ) {
	    this.log( e, name, value );
	    trans.setVersion(value);
	  } else if ( name.equals("argumentSeparator") ) {
	    this.log( e, name, value );
	    trans.setArgumentSeparator(value);
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return trans;
      }

      // unknown 
      return null;

      //
      // U
      //
    case 'u':
      if ( e.equals("use") ) {
	Use use = new Use();
	for ( int i=0; i<names.size(); ++i ) {
	  String name = (String) names.get(i);
	  String value = (String) values.get(i);

	  if ( name.equals("name") ) {
	    this.log( e, name, value );
	    use.setName(value);
	  } else if ( name.equals("prefix") ) {
	    this.log( e, name, value );
	    use.setPrefix(value);
	  } else if ( name.equals("separator") ) {
	    this.log( e, name, value );
	    use.setSeparator(value);
	  } else if ( name.equals("suffix") ) {
	    this.log( e, name, value );
	    use.setSuffix(value);
	  } else if ( name.equals("link") ) {
	    this.log( e, name, value );
	    use.setLink( VDLType.getLinkType(value) );
	  } else {
	    this.complain( e, name, value );
	  }
	}
	return use;
      }

      // unknown 
      return null;

    default:
      // FIXME: shouldn't this be an exception?
      Logging.instance().log( "filler", 0,
			      "Error: No rules defined for element " + e );
      return null;
    }
  }

  /**
   * This method sets the relations between the currently finished XML
   * element and its containing element in terms of Java objects.
   * Usually it involves adding the object to the parent's child object
   * list.
   *
   * @param initial is the first charactor of the parent element name
   * @param parent is a reference to the parent's Java object
   * @param child is the completed child object to connect to the parent
   * @return true if the element was added successfully, false, if the
   * child does not match into the parent.
   */
  protected boolean setElementRelation( char initial, VDL parent, VDL child )
  {
    switch ( initial ) {
      //
      // A
      //
    case 'a':
      if ( parent instanceof Argument && child instanceof Leaf ) {
	// addLeaf is self-checking
	((Argument) parent).addLeaf((Leaf) child);
	return true;
      }
      return false;

      //
      // C
      //
    case 'c':
      if ( parent instanceof Call ) {
	Call c = (Call) parent;
	if ( child instanceof Pass ) {
	  ((Call) parent).addPass((Pass) child);
	  return true;
	} else if ( child instanceof Meta ) {
	  // dunno
	  return true;
	}
      }
      return false;

      //
      // D
      //
    case 'd':
      if ( parent instanceof Declare && child instanceof Value ) {
	// setValue is self-checking
	((Declare) parent).setValue((Value) child);
	return true;

      } else if ( parent instanceof Definitions && child instanceof Definition ) {
	// again, self-checking
	((Definitions) parent).addDefinition((Definition) child);

	// This means we have finished parsing a TR or DV. Invoke callback!
	if ( m_callback != null ) {
	  // Logging.instance().log( "app", 3, "invoking callback for " + ((Definition) child).shortID() );
	  m_callback.store((Definition) child);
	}

	// FIXME: decrease memory footprint!
	((Definitions) parent).removeAllDefinition();
	return true;

      } else if ( parent instanceof Derivation ) {
	Derivation d = (Derivation) parent;
	if ( child instanceof Pass ) {
	  d.addPass((Pass) child);
	  return true;
	} else if ( child instanceof Meta ) {
	  // dunno
	  return true;
	}
      }

      // unknown 
      return false;

      //
      // L 
      //
    case 'l':
      if ( parent instanceof org.griphyn.vdl.classes.List && child instanceof Scalar ) {
	((org.griphyn.vdl.classes.List) parent).addScalar((Scalar) child);
	return true;
      } else if ( parent instanceof Local && child instanceof Value ) {
	// setValue is self-checking
	((Local) parent).setValue((Value) child);
	return true;
      }

      // LFN is a *leaf* !!

      // unknown 
      return false;

      //
      // M
      //
    case 'm':
      if ( parent instanceof Meta ) {
	// dunno whatta do
	return true;
      }

      // unknown
      return false;

      //
      // P
      //
    case 'p':
      if ( parent instanceof Profile && child instanceof Leaf ) {
	((Profile) parent).addLeaf((Leaf) child);
	return true;

      } else if ( parent instanceof Pass && child instanceof Value ) {
	((Pass) parent).setValue((Value) child);
	return true;
      }

      // unknown 
      return false;

      //
      // S
      //
    case 's':
      if ( parent instanceof Scalar && child instanceof Leaf ) {
	((Scalar) parent).addLeaf((Leaf) child);
	return true;
      }

      // unknown 
      return false;

      //
      // T
      //
    case 't':
      if ( parent instanceof Transformation ) {
	Transformation trans = (Transformation) parent;
	if ( child instanceof Declare ) {
	  trans.addDeclare((Declare) child);
	  return true;
	} else if ( child instanceof Argument ) {
	  trans.addArgument((Argument) child);
	  return true;
	} else if ( child instanceof Call ) {
	  trans.addCall((Call) child);
	  return true;
	} else if ( child instanceof Local ) {
	  trans.addLocal((Local) child);
	  return true;
	} else if ( child instanceof Profile ) {
	  trans.addProfile((Profile) child);
	  return true;
	} else if ( child instanceof Meta ) {
	  // dunno
	  return true;
	}
      }
      // Text is a *leaf* !!
      return false;

      //
      // U
      //
    case 'u':
      // Use is a *leaf* !!
      return false;

    default:
      // FIXME: shouldn't this be an exception?
      Logging.instance().log( "filler", 0,
			      "Error: unable to join child to parent" );
      return false;
    }
  }
}
